﻿יצירת מודל
==============

לפני כתיבת קוד ה HTML הנחוץ לטופס, אנו צריכים להחליט אילו נתונים אנו מצפים לקבל ממשתמשי הקצה ואילו חוקים אותם נתונים צריכים לעמוד בהם. ניתן להעזר במודל בישביל לתעד מידע זה. [מודל](/doc/guide/basics.model), כפי שהוגדר בחלק לגבי מודלים, הוא המקום המרכזי עבור שמירת נתונים ואימות הנתונים המגיעים מהמשתמשים.

תלוי באופן השימוש שלנו בנתונים המגיעים מהמשתמשים, אנו יכולים ליצור שני סוגים של מודלים. אם המידע שהמשתמש מזין נאסף, משומש ואז נזרק, אנו ניצור [מודל טופס](/doc/guide/basics.model); אם המידע של המשתמש נאסף ונשמר במסד הנתונים, אנו ניצור מודל [AR](/doc/guide/database.ar) במקום. שני המחלקות יורשות מהמחלקה הבסיסית [CModel] שמגדירים את הממשק הכללי הדרוש בטופס.

» Note|הערה: בדוגמאות בחלק זה אנו בעיקר משתמשים במודל טופס. למרות, שניתן להשתמש באותם דוגמאות על גבי מודל [AR](/doc/guide/database.ar).

הגדרת מחלקת מודל
--------------------

למטה אנו יוצרים מחלקת מודל בשם `LoginForm` שאנו נעזרים בה לאיסוף מידע שמשתמש מזין בעמוד ההתחברות. מאחר והמידע של ההתחברות נועד למטרת אימות בלבד ואין צורך לשמור אותו, אנו יוצרים את ה `LoginForm` כמודל טופס.

~~~
[php]
class LoginForm extends CFormModel
{
    public $username;
    public $password;
    public $rememberMe=false;
}
~~~

שלושה משתנים מוגדרים ב `LoginForm`: המשתנים המוגדרים הם `username$`, `password$` ו `rememberMe$`. הם נועדו כדי לאפשר למשתמש להזין שם משתמש וסיסמא, ואפשרות לבחור במידה והוא רוצה לשמור את ההתחברות שלו לתקופה ממושכת. מאחר ולמשתנה `rememberMe$` ישנו ערך ברירת מחדל של `false`, כפתור הבחירה של המשתנה הנלמד יהיה לא מסומן כברירת מחדל בטופס.

» Info|מידע: במקום לקרוא למשתנים הללו מאפיינים, אנו משתמשים במונח *משתנים* בכדי להבדיל אותם ממאפיינים רגילים. מאפיינים נועדו בעיקר לשמירת מידע המגיע מהמשתמש.

הגדרת חוקי אימות נתונים
--------------------------

ברגע שהמשתמש שולח את הנתונים שהוא הזין והמודל מאוכלס בנתונים אלו, אנו צריכים לוודא שהנתונים תקינים לפני שאנו משתמשים בהם. זה נעשה על ידי ביצוע אימות נתונים על גבי סט של חוקים. אנו מגדירים את חוקי אימות הנתונים במתודה `()rules` שצריכה להחזיר מערך של הגדרות.

~~~
[php]
class LoginForm extends CFormModel
{
    public $username;
    public $password;
    public $rememberMe=false;

    private $_identity;

    public function rules()
    {
        return array(
            array('username, password', 'required'),
            array('rememberMe', 'boolean'),
            array('password', 'authenticate'),
        );
    }

    public function authenticate($attribute,$params)
    {
        $this-»_identity=new UserIdentity($this-»username,$this-»password);
        if(!$this-»_identity-»authenticate())
            $this-»addError('password','Incorrect username or password.');
    }
}
~~~

הקוד למעלה מציין ש `username` ו `password` שניהם הם שדות חובה, `password` צריך להיות מאומת, ו `rememberMe` צריך להיות ערך בוליאני (boolean).

כל חוק הנמצא במתודה `()rules` צריך להיות מוגדר בפורמט הבא:

~~~
[php]
array('AttributeList', 'Validator', 'on'=»'ScenarioList', ...additional options)
~~~

כשהערך  `AttributeList` הינו סטרינג המופרד בפסיקים של שמות שצריך לאמת אותם מול החוק שהוגדר; `Validator` מגדיר את סוג הבדיקה שיש לבצע על גבי המשתנים שהוגדרו במשתנה קודם לכן; `on` הינו ערך אופציונלי המגדיר באילו תסריטים (מצבים) יש לבצע את האימות הנוכחי; ואפשרויות נוספות הינם זוגות של מפתח=»ערך שנועדו לאתחול של המאפיינים בחוק הנוכחי.

ישנם שלושה דרכים להגדרת `Validator` בחוק. אפשרות ראשונה, `Validator` יכול להיות שמה של מתודה במחלקת המודל הנוכחי, כמו `authenticate` בדוגמא למעלה. המתודה צריכה להראות בצורה הבאה:

~~~
[php]
/**
 * @param string the name of the attribute to be validated
 * @param array options specified in the validation rule
 */
public function ValidatorName($attribute,$params) { ... }
~~~

אפשרות שנייה, `Validator` יכול להיות שם של מחלקת אימות נתונים. כשהחוק יצורף, יווצר אובייקט של המחלקה אשר יבצע את הבדיקה. האפשרויות הנוספות בהגדרת החוק (כפי שתואר למעלה) נועדו כדי להגדיר את מאפייני המחלקה בעת יצירת האובייקט של המחלקה. מחלקת אימות נתונים צריכה להיות תת מחלקה של [CValidator].

» Note|הערה: ברגע שמגדירים חוקים למודל AR, אנו יכולים להשתמש באפשרות מיוחדת בשם `on`. האפשרות יכולה להיות `insert` או `update` כדי שהחוק המדובר יצורף רק כשמוסיפים או מעדכנים רשומה, בהתאמה. במידה ולא הוגדר, החוק יצורף בשני המקרים בעת הקריאה ל `()save`.

אפשרות שלישית, `Validator` יכול להוות שם מקוצר למחלקות אימות נתונים שהוגדרו מראש. בדוגמא למעלה, השם `required` הינו שם מקוצר למחלקה [CRequiredValidator] אשר מוודא שהערך של המשתנה הנבדק אינו ריק. למטה מוצגת הרשימה המלאה לשמות מקוצרים למחלקות בדיקות נתונים המוגדרות מראש:


- `boolean`: שם מקוצר עבור המחלקה [CBooleanValidator], מוודא שהמשתנה מכיל ער של
 [CBooleanValidator::trueValue] או
[CBooleanValidator::falseValue].

- `captcha`: שם מקוצר עבור המחלקה [CCaptchaValidator], המוודא שהערך שהוזן זהה לערך שהוצג על ידי
[CAPTCHA](http://en.wikipedia.org/wiki/Captcha).

- `compare`: שם מקוצר עבור המחלקה [CCompareValidator], מוודא שהערך שנבדק זהה לערך הנוסף שהוגדר.

- `email`: שם מקוצר עבור המחלקה [CEmailValidator], מוודא שהערך הנבדק הינו אימייל תקני.

- `default`: שם מקוצר עבור המחלקה [CDefaultValueValidator], מגדיר ערכים ברירת מחדל עבור המשתנים הנבדקים.

- `exist`: שם מקוצר עבור המחלקה [CExistValidator], מוודא שהערך הנבדק קיים בעמודה בטבלה הנוכחית.

- `file`: שם מקוצר עבור המחלקה [CFileValidator], מוודא שהערך הנבדק מכיל את שם הקובץ שהועלה.

- `filter`: שם מקוצר עבור המחלקה [CFilterValidator], המרת הערך הנבדק דרך פילטר שהוגדר.

- `in`: שם מקוצר עבור המחלקה [CRangeValidator], מוודא שהערך הנבדק הוא בין רשימת הערכים שהוגדרו.

- `length`: שם מקוצר עבור המחלקה [CStringValidator], מוודא שאורך הערך הנבדק הוא באורך מסויים.

- `match`: שם מקוצר עבור המחלקה [CRegularExpressionValidator], מוודא שהערך תואם לביטוי רגולרי.

- `numerical`: שם מקוצר עבור המחלקה [CNumberValidator], מוודא שהערך הנבדק הוא מספר.

- `required`: שם מקוצר עבור המחלקה [CRequiredValidator], מוודא שהערך הנבדק אינו ריק.

- `type`: שם מקוצר עבור המחלקה [CTypeValidator], מוודא שהערך הנבדק הוא מסוג מסויים.

- `unique`: שם מקוצר עבור המחלקה [CUniqueValidator], מוודא שהערך הינו יחודי בעמודה בטבלה.

- `url`: שם מקוצר עבור המחלקה [CUrlValidator], מוודא שהערך הנבדק הינו קישור תקני.

למטה מוצגים כמה דוגמאות לשימוש במחלקות אימות נתונים המוגדרים מראש:

~~~
[php]
// שם משתמש הוא הכרחי
array('username', 'required'),
// שם משתמש חייב להיות בין 3 ל 12 תווים
array('username', 'length', 'min'=»3, 'max'=»12),
// בזמן הרשמה הסיסמא צריכה להיות תואמת לאישור הסיסמא
array('password', 'compare', 'compareAttribute'=»'password2', 'on'=»'register'),
// בזמן התחברות הסיסמא צריכה להיות מאומתת
array('password', 'authenticate', 'on'=»'login'),
~~~


אבטחת הצבת מאפיינים
------------------------------

לאחר יצירת אובייקט של מודל, אנו בדרך כלל צריכים לאכלס את המאפיינים שלו על ידי המידע שהמשתמש הזין. ניתן לבצע זאת בצורה פשוטה ונוחה על ידי הצבה מאסיבית של מאפיינים:

~~~
[php]
$model=new LoginForm;
if(isset($_POST['LoginForm']))
    $model-»attributes=$_POST['LoginForm'];
~~~

הפקודה האחרונה נקראת *הצבה מאסיבית* אשר מציבה את כל המאפיינים ב `$_POST['LoginForm']` למאפיינים המתאימים במודל. פקודה זו זהה לקוד הבא:

~~~
[php]
foreach($_POST['LoginForm'] as $name=»$value)
{
    if($name is a safe attribute)
        $model-»$name=$value;
}
~~~

זה הכרחי להגדיר אילו מאפיינים הם מאובטחים. לדוגמא, במידה ונחשוף את העמודה השומרת את המפתח הראשי בטבלה ונגדיר אותו להיות מאפיין מאובטח, ניתן יהיה לשנות את המפתח הראשי של מידע מסויים ובכך לקבל גישה לנתונים שמשתמשים לא צריכים לראות.

התנאים בהגדרת מאפיינים בטוחים שונים בין הגרסאות 1.0 ו 1.1. בחלק הבא, אנו נסביר לגביהם בנפרד.

### מאפיינים בטוחים ב 1.1

בגרסא 1.1, מאפיין מוגדר כמאובטח אם הוא מופיע בחוקים לאימות הנתונים המוגדרים לעבוד עם התסריט הנוכחי. לדוגמא,

~~~
[php]
array('username, password', 'required', 'on'=»'login, register'),
array('email', 'required', 'on'=»'register'),
~~~

בקוד למעלה, המאפיינים `username` ו `password` הם שדו חובה בתסריט של `login`, בזמן שהמאפיינים `username`, `password` ו `email` הם שדות חובה בתסריט של `register`. כתוצאה מכך, אם נבצע הצבה מאסיבית של נתונים למאפיינים כשאנו נמצאים בתסריט של `login`, רק המאפיינים `username` ו `password` יוצבו בצורה מאסיבית מאחר ורק מאפיינים אלו מופיעים בחוקים עבור התסריט `login`.
מצד שני, אם התסריט הוא `register`, כל שלושת המאפיינים יוצבו בצורה מאסיבית.

~~~
[php]
// בתסריט התחברות
$model=new User('login');
if(isset($_POST['User']))
    $model-»attributes=$_POST['User'];

// בתסריט הרשמה
$model=new User('register');
if(isset($_POST['User']))
    $model-»attributes=$_POST['User'];
~~~

אז למה אנחנו משתמשים במדיניות זו בכדי לקבוע אם מאפיין הוא בטוח או לא?
הלוגיקה מאחורי זה היא אם מאפיין כבר מוגדר עם חוק אחד או יותר לאימות הנתונים בו, למה לנו לדאוג בנוגע אליו?

חשוב לזכור שחוקי אימות הנתונים נועדו לאמת את הנתונים המגיעים מהמשתמש ולא את הנתונים שאנו יוצרים בקוד (לדוגמא זמן נוכחי, ערך המפתח הראשי שנוצר אוטומטית).
לכן, אין להוסיף חוקים לאימות הנתונים לנתונים שלא מגיעים מהמשתמשים.

לפעמים, אנחנו מגדירים מאפיין בטוח, למרות שאין לנו חוק מסויים עבורו. לדוגמא מאפיין של תוכן מאמר שיכול להכיל כל תוכן שהמשתמש מזין. אנו יכולים להשתמש בחוק מיוחד בשם `safe` בכדי להשיג מטרה זו:

~~~
[php]
array('content', 'safe')
~~~

לצורך השלמת הקוד, ישנו חוק בשם `unsafe` שנועד להגדיר מאפיין כלא בטוח ולא להכליל אותו בנתונים:

~~~
[php]
array('permission', 'unsafe')
~~~

בדרך כלל אין שימוש בחוק `unsafe`, והוא ההפך ממה שדובר לגבי חוק ה `safe` עבור מאפיינים בטוחים.

### מאפיינים בטוחים ב 1.0

בגרסאות 1.0, ההחלטה בין אם מאפיין הוא בטוח או לא מבוססת על הערך המוחזר מהמתודה `safeAttributes` והתסריט המוגדר כרגע. כברירת מחדל, המתודה מחזירה את כל מאפייני המחלקה [CFormModel] כמאפיינים בטוחים, בזמן שהינה מחזירה את כל העמודות בטבלה מלבד העמודה של המפתח הראשי עבור המחלקה [CActiveRecord].
אנו יכולים לדרוס מתודה זו בכדי להגביל את המאפיינים הבטוחים לפי התסריט המוגדר.
לדוגמא, מודל משתמשים יכול להכיל מאפיינים רבים, אבל בזמן השימוש בתסריט `login` אנו נצטרך רק את המאפיינים של שם המשתמש והסיסמא, `username` ו  `password`.
אנו יכולים להגדיר את ההגבלה בצורה הבאה:

~~~
[php]
public function safeAttributes()
{
    return array(
        parent::safeAttributes(),
        'login' =» 'username, password',
    );
}
~~~

יותר מדוייק, הערך המוחזר מהמתודה `safeAttributes` צריך להראות כמו התבנית הבאה:

~~~
[php]
array(
   // מאפיינים אלו ניתנים להצבה מאסיבית בכל תסריט
   // שלא הוגדר ספציפית כמו בדוגמאות למטה
   'attr1, attr2, ...',
     *
   // מאפיינים אלו ניתנים להצבה מאסיבית רק בזמן השימוש בתסריט מספר 1
   'scenario1' =» 'attr2, attr3, ...',
     *
   // מאפיינים אלו ניתנים להצבה מאסיבית רק בזמן השימוש בתסריט מספר 2
   'scenario2' =» 'attr1, attr3, ...',
)
~~~

במידה והמודל אינו רגיש כלפי תסריט (כלומר, משתמשים בו עם תסריט אחד בלבד, או כל התסריטים משתמשים באותם המאפיינים הבטוחים), ניתן לפשט את הערך המוחזר בסטרינג בודד:

~~~
[php]
'attr1, attr2, ...'
~~~

עבור מאפיינים שהם לא בטוחים, אנו צריכים להגדיר אותם בצורה פרטנית בהתאם לערכים שהם אמורים להכיל, לדוגמא,

~~~
[php]
$model-»permission='admin';
$model-»id=1;
~~~


ביצוע אימות הנתונים
---------------------

לאחר שהמודל מאוכלס עם הנתונים שהמשתמש הזין, אנו קוראים למתודה [()CModel::validate] בכדי להריץ את תהליך אימות הנתונים. המתודה מחזירה ערך המציין אם תהליך אימות הנתונים עבר בהצלחה או לא. עבור מודלים של [CActiveRecord], אימות הנתונים יתבצע בצורה אוטומטית ברגע שנקרא למתודה [()CActiveRecord::save].

אנו יכולים לציין את התסריט הנוכחי על ידי הגדרת המאפיין [scenario|CModel::scenario] ועל ידי כך לציין אילו חוקים יצורפו לבדיקה זו.

אימות הנתונים מתבצע על בסיס התסריט. המאפיין [scenario|CModel::scenario] מגדיר איזה תסריט המודל כרגע משתמש בו ובאילו חוקים יש להשתמש באימות הנתונים כרגע. לדוגמא, בתסריט של `login`, אנו נרצה רק לאמת את שם המשתמש והסיסמא - `username` ו `password` הנמצאים במודל של המשתמשים; בזמן שבתסריט `register`, אנו נצטרך לאמת יותר נתונים, כמו `email`, `address`, וכדומה.
הדוגמא הבאה מציגה כיצד לבצע אימות נתונים תחת תסריט `register`:

~~~
[php]
// יוצר מודל כשמצב התסריט מוגדר ל `register`. הקוד זהה ל:
// $model=new User;
// $model-»scenario='register';
$model=new User('register');

// מאכלס את הנתונים שהגיע מהמשתמש אל המודל
$model-»attributes=$_POST['User'];

// מבצע את הבדיקה
if($model-»validate())   // במידה והנתונים תקינים
    ...
else
    ...
~~~

בכדי להגדיר חוקים מסויימים עבור תסריטים מסויימים יש להגדיר את המאפיין `on` בהגדרה של החוק עצמו. במידה והמאפיין `on` לא הוגדר, זה אומר שהחוק צריך להיות תקף בכל התסריטים האפשריים. לדוגמא,

~~~
[php]
public function rules()
{
    return array(
        array('username, password', 'required'),
        array('password_repeat', 'required', 'on'=»'register'),
        array('password', 'compare', 'on'=»'register'),
    );
}
~~~

החוק הראשון יצורף לכל התסריטים, בזמן ששני הבאים יצורפו רק לתסריט של `register`.


קבלת שגיאות אימות נתונים
----------------------------

לאחר ביצוע האימות, כל השגיאות הקיימות יאוחסנו תחת האובייקט של המודל. אנו שולפים את הודעות השגיאות על ידי קריאה למתודה [()CModel::getErrors]
או [()CModel::getError]. ההבדל בין השניים הוא שהראשון יחזיר את *כל* השגיאות עבור מאפיין כלשהו במודל. בזמן שהשני מחזיר את השגיאה *הראשונה* עבור מאפיין כלשהו.

תוויות מאפיינים
----------------

בעת עיצוב טופס, אנחנו בדרך כלל צריכים להציג תווית עבור כל שדה. התוית אומרת למשתמש איזה סוג מידע הוא צריך להזין לשדה. למרות שאנו יכולים להוסיף זאת ישירות בתוך קוד ה HTML, יש עדיפות להגדיר את התויות של השדות במחלקה של המודל בכדי לקבל יותר גמישות ולנוחות השימוש.

כברירת מחדל, [CModel] יפשט את החזרת השם של מאפיין כלשהו המוגדר בו. ניתן להגדיר זאת על ידי דריסה של המתודה [()attributeLabels|CModel::attributeLabels]. כפי שאנו נראה בחלקים הבאים, הגדרת התויות של מאפיינים במודל יאפשר לנו ליצור טופס בצורה הרבה יותר מהירה ויעילה.


«div class="revision"»$Id: form.model.txt 1919 2010-03-15 17:25:48Z qiang.xue $«/div»